home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Visual Cafe 3
/
Visual Cafe 3.ISO
/
Vcafe
/
JFC.bin
/
BasicComboPopup.java
< prev
next >
Wrap
Text File
|
1998-06-30
|
27KB
|
774 lines
/*
* @(#)BasicComboPopup.java 1.3 98/04/10
*
* Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*
*/
package com.sun.java.swing.plaf.basic;
import com.sun.java.swing.*;
import com.sun.java.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.io.Serializable;
/**
* This is an implementation of the ComboPopup interface. It is primarily for use by
* BasicComboBoxUI and its subclasses. BasicComboPopup extends JPopupMenu because
* most combo boxes use a popup menu to display the list of possible selections.
* BasicComboBoxUI only requires a ComboPopup, so subclasses of BasicComboBoxUI aren't
* required to use this class.
*
* All event handling is handled by createxxxListener() methods and internal classes.
* You can change the behavior of this class by overriding the createxxxListener()
* methods and supplying your own event listeners or subclassing from the ones supplied
* in this class.
*
* Inner classes for handling events:
* InvocationMouseListener
* InvocationMouseMotionListener
* InvocationKeyListener
* ListSelListener
* ListMouseListener
* ListMouseMotionListener
* ComboPropertyChangeListener
* ComboItemListener
*
* <p>
* Warning: serialized objects of this class will not be compatible with
* future swing releases. The current serialization support is appropriate
* for short term storage or RMI between Swing1.0 applications. It will
* not be possible to load serialized Swing1.0 objects with future releases
* of Swing. The JDK1.2 release of Swing will be the compatibility
* baseline for the serialized form of Swing objects.
*
* @version 1.3 04/10/98
* @author Tom Santos
*/
public class BasicComboPopup extends JPopupMenu implements ComboPopup {
protected JComboBox comboBox;
protected JList list;
protected JScrollPane scroller;
// If the value is adjusting, any changes to the list selection won't affect the model.
protected boolean valueIsAdjusting = false;
// Listeners that are required by the ComboPopup interface
protected MouseMotionListener mouseMotionListener;
protected MouseListener mouseListener;
protected KeyListener keyListener;
// Listeners that are attached to the list
protected ListSelectionListener listSelectionListener;
protected MouseListener listMouseListener;
protected MouseMotionListener listMouseMotionListener;
// Listeners that are attached to the JComboBox
protected PropertyChangeListener propertyChangeListener;
protected ItemListener itemListener;
protected Timer autoscrollTimer;
protected boolean hasEntered = false;
protected boolean isAutoScrolling = false;
protected int scrollDirection = SCROLL_UP;
protected static final int SCROLL_UP = 0;
protected static final int SCROLL_DOWN = 1;
//========================================
// begin ComboPopup method implementations
//
/**
* Implementation of ComboPopup.show().
*/
public void show() {
Dimension popupSize = comboBox.getSize();
popupSize.setSize( popupSize.width, getPopupHeightForRowCount( comboBox.getMaximumRowCount() ) );
scroller.setMaximumSize( popupSize );
scroller.setPreferredSize( popupSize );
scroller.setMinimumSize( popupSize );
Rectangle popupBounds = computePopupBounds( 0, comboBox.getBounds().height,
popupSize.width, popupSize.height);
list.invalidate();
list.setSelectedIndex( comboBox.getSelectedIndex() );
list.ensureIndexIsVisible( list.getSelectedIndex() );
setLightWeightPopupEnabled( comboBox.isLightWeightPopupEnabled() );
show( comboBox, popupBounds.x, popupBounds.y );
}
/**
* Implementation of ComboPopup.hide().
*/
public void hide() {
setVisible( false );
comboBox.repaint();
}
/**
* Implementation of ComboPopup.getMouseListener().
*/
public MouseListener getMouseListener() {
return mouseListener;
}
/**
* Implementation of ComboPopup.getMouseMotionListener().
*/
public MouseMotionListener getMouseMotionListener() {
return mouseMotionListener;
}
/**
* Implementation of ComboPopup.getKeyListener().
*/
public KeyListener getKeyListener() {
return keyListener;
}
/**
* Called when the UI is uninstalling. Since this popup isn't in the component
* tree, it won't get it's uninstallUI() called. It removes the listeners that
* were added in addComboBoxListeners().
*/
public void uninstallingUI() {
comboBox.removePropertyChangeListener( propertyChangeListener );
comboBox.removeItemListener( itemListener );
}
//
// end ComboPopup method implementations
//======================================
//===================================================================
// begin Initialization routines
//
public BasicComboPopup( JComboBox combo ) {
super();
comboBox = combo;
mouseListener = createMouseListener();
mouseMotionListener = createMouseMotionListener();
keyListener = createKeyListener();
listSelectionListener = createListSelectionListener();
listMouseListener = createListMouseListener();
listMouseMotionListener = createListMouseMotionListener();
propertyChangeListener = createPropertyChangeListener();
itemListener = createItemListener();
list = createList();
configureList();
scroller = createScroller();
configureScroller();
configurePopup();
addComboBoxListeners();
}
/**
* Creates the mouse listener that is returned by ComboPopup.getMouseListener().
* Returns an instance of BasicComboPopup$InvocationMouseListener.
*/
protected MouseListener createMouseListener() {
return new InvocationMouseListener();
}
/**
* Creates the mouse motion listener that is returned by
* ComboPopup.getMouseMotionListener().
* Returns an instance of BasicComboPopup$InvocationMouseMotionListener.
*/
protected MouseMotionListener createMouseMotionListener() {
return new InvocationMouseMotionListener();
}
/**
* Creates the key listener that is returned by ComboPopup.getKeyListener().
* Returns an instance of BasicComboPopup$InvocationKeyListener.
*/
protected KeyListener createKeyListener() {
return new InvocationKeyListener();
}
/**
* Creates a list selection listener that watches for selection changes in
* the popup's list.
* Returns an instance of BasicComboPopup$ListSelListener.
*/
protected ListSelectionListener createListSelectionListener() {
return new ListSelListener();
}
/**
* Creates a mouse listener that watches for mouse events in
* the popup's list.
* Returns an instance of BasicComboPopup$ListMouseListener.
*/
protected MouseListener createListMouseListener() {
return new ListMouseListener();
}
/**
* Creates a mouse motion listener that watches for mouse events in
* the popup's list.
* Returns an instance of BasicComboPopup$ListMouseMotionListener.
*/
protected MouseMotionListener createListMouseMotionListener() {
return new ListMouseMotionListener();
}
/**
* Creates a property change listener that watches for changes in the bound
* properties in the JComboBox.
* Returns an instance of BasicComboPopup$ComboPropertyChangeListener.
*/
protected PropertyChangeListener createPropertyChangeListener() {
return new ComboPropertyChangeListener();
}
/**
* Creates an item listener that watches for changes in the selected
* item in the JComboBox.
* Returns an instance of BasicComboPopup$ComboItemListener.
*/
protected ItemListener createItemListener() {
return new ComboItemListener();
}
/**
* Creates the JList that is used in the popup to display the items in the model.
*/
protected JList createList() {
return new JList( comboBox.getModel() );
}
/**
* Called to configure the list created by createList().
*/
protected void configureList() {
list.setFont( comboBox.getFont() );
list.setForeground( comboBox.getForeground() );
list.setBackground( comboBox.getBackground() );
list.setSelectionForeground( UIManager.getColor( "ComboBox.selectedForeground" ) );
list.setSelectionBackground( UIManager.getColor( "ComboBox.selectedBackground" ) );
list.setBorder( null );
list.setCellRenderer( comboBox.getRenderer() );
list.setRequestFocusEnabled( false );
addListListeners();
}
/**
* Called by configureList() to add the necessary listeners to the list.
*/
protected void addListListeners() {
list.addListSelectionListener( listSelectionListener );
list.addMouseMotionListener( listMouseMotionListener );
list.addMouseListener( listMouseListener );
}
/**
* Creates the JScrollPane that is used in the popup to hold the list.
*/
protected JScrollPane createScroller() {
return new JScrollPane( list, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
}
/**
* Called to configure the JScrollPane created by createScroller().
*/
protected void configureScroller() {
scroller.setRequestFocusEnabled( false );
scroller.getVerticalScrollBar().setRequestFocusEnabled( false );
scroller.setBorder( null );
}
/**
* Called to configure this JPopupMenu (BasicComboPopup is a JPopupMenu).
*/
protected void configurePopup() {
setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) );
setBorderPainted( true );
setBorder( BorderFactory.createLineBorder( Color.black ) );
setOpaque( false );
add( scroller );
setDoubleBuffered( true );
setRequestFocusEnabled( false );
}
/**
* This method adds the necessary listeners to the JComboBox.
*/
protected void addComboBoxListeners() {
comboBox.addPropertyChangeListener( propertyChangeListener );
comboBox.addItemListener( itemListener );
}
//
// end Initialization routines
//=================================================================
//===================================================================
// begin Event Listenters
//
/**
* This listener knows how and when to invoke this popup menu. It also helps
* with click-and-drag scenarios by setting the selection if the mouse was
* released over the list during a drag.
*/
protected class InvocationMouseListener extends MouseAdapter {
public void mousePressed( MouseEvent e ) {
Rectangle r;
if ( !SwingUtilities.isLeftMouseButton(e) )
return;
if ( !comboBox.isEnabled() )
return;
delegateFocus( e );
togglePopup();
}
public void mouseReleased( MouseEvent e ) {
Component source = (Component)e.getSource();
Dimension size = source.getSize();
Rectangle bounds = new Rectangle( 0, 0, size.width - 1, size.height - 1 );
if ( !bounds.contains( e.getPoint() ) ) {
MouseEvent newEvent = convertMouseEvent( e );
Point location = newEvent.getPoint();
Rectangle r = new Rectangle();
list.computeVisibleRect( r );
if ( r.contains( location ) ) {
updateListBoxSelectionForEvent( newEvent, false );
comboBox.setSelectedIndex( list.getSelectedIndex() );
}
hide();
}
hasEntered = false;
stopAutoScrolling();
}
}
/**
* This listener watches for dragging and updates the current selection in the
* list if it is dragging over the list.
*/
protected class InvocationMouseMotionListener extends MouseMotionAdapter {
public void mouseDragged( MouseEvent e ) {
if ( isVisible() ) {
MouseEvent newEvent = convertMouseEvent( e );
Rectangle r = new Rectangle();
list.computeVisibleRect( r );
if ( newEvent.getPoint().y >= r.y && newEvent.getPoint().y <= r.y + r.height - 1 ) {
hasEntered = true;
if ( isAutoScrolling ) {
stopAutoScrolling();
}
Point location = newEvent.getPoint();
if ( r.contains( location ) ) {
valueIsAdjusting = true;
updateListBoxSelectionForEvent( newEvent, false );
valueIsAdjusting = false;
}
}
else {
if ( hasEntered ) {
int directionToScroll = newEvent.getPoint().y < r.y ? SCROLL_UP : SCROLL_DOWN;
if ( isAutoScrolling && scrollDirection != directionToScroll ) {
stopAutoScrolling();
startAutoScrolling( directionToScroll );
}
else if ( !isAutoScrolling ) {
startAutoScrolling( directionToScroll );
}
}
else {
if ( e.getPoint().y < ((Component)e.getSource()).getBounds().y ) {
hasEntered = true;
startAutoScrolling( SCROLL_UP );
}
}
}
}
}
}
/**
* This listener watches for the spacebar being pressed and shows/hides the
* popup accordingly.
*/
public class InvocationKeyListener extends KeyAdapter {
public void keyReleased( KeyEvent e ) {
if ( e.getKeyCode() == KeyEvent.VK_SPACE ) {
togglePopup();
}
}
}
/**
* This listener watches for changes in the list's selection and reports
* them to the combo box.
*/
protected class ListSelListener implements ListSelectionListener {
public void valueChanged( ListSelectionEvent e ) {
if ( isVisible() && !valueIsAdjusting && !e.getValueIsAdjusting() ) {
comboBox.setSelectedIndex( list.getSelectedIndex() );
}
}
}
/**
* This listener hides the popup when the mouse is released in the list.
*/
protected class ListMouseListener extends MouseAdapter {
public void mouseReleased(MouseEvent anEvent) {
hide();
}
}
/**
* This listener changes the selected item as you move the mouse over the list.
* The selection change is not committed to the model, this is for user feedback only.
*/
protected class ListMouseMotionListener extends MouseMotionAdapter {
public void mouseMoved( MouseEvent anEvent ) {
Point location = anEvent.getPoint();
Rectangle r = new Rectangle();
list.computeVisibleRect( r );
if ( r.contains( location ) ) {
valueIsAdjusting = true;
updateListBoxSelectionForEvent( anEvent, false );
valueIsAdjusting = false;
}
}
}
/**
* This listener watches for changes in the JComboBox's selection. It updates
* the list accordingly.
*/
protected class ComboItemListener implements ItemListener {
public void itemStateChanged( ItemEvent e ) {
if ( e.getStateChange() == ItemEvent.SELECTED ) {
valueIsAdjusting = true;
list.setSelectedIndex( comboBox.getSelectedIndex() );
valueIsAdjusting = false;
list.ensureIndexIsVisible( comboBox.getSelectedIndex() );
}
}
}
/**
* This listener watches for bound property changes in JComboBox. If the model
* or the renderer changes, the popup hides itself.
*/
protected class ComboPropertyChangeListener implements PropertyChangeListener {
public void propertyChange( PropertyChangeEvent e ) {
String propertyName = e.getPropertyName();
if ( propertyName.equals("model") ) {
list.setModel( comboBox.getModel() );
if ( isVisible() ) {
hide();
}
}
else if ( propertyName.equals( "renderer" ) ) {
list.setCellRenderer( comboBox.getRenderer() );
if ( isVisible() ) {
hide();
}
}
}
}
//
// end Event Listenters
//=================================================================
/**
* Overridden to unconditionally return false.
*/
public boolean isFocusTraversable() {
return false;
}
//===================================================================
// begin Autoscroll methods
//
/**
* Called by BasicComboPopup$InvocationMouseMotionListener to handle auto-
* scrolling the list.
*/
protected void startAutoScrolling( int direction ) {
if ( isAutoScrolling ) {
autoscrollTimer.stop();
}
isAutoScrolling = true;
if ( direction == SCROLL_UP ) {
scrollDirection = SCROLL_UP;
Point convertedPoint = SwingUtilities.convertPoint( scroller, new Point( 1, 1 ), list );
int top = list.locationToIndex( convertedPoint );
valueIsAdjusting = true;
list.setSelectedIndex( top );
valueIsAdjusting = false;
AbstractAction timerAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
autoScrollUp();
}
public boolean isEnabled() {
return true;
}
};
autoscrollTimer = new Timer( 100, timerAction );
}
else if ( direction == SCROLL_DOWN ) {
scrollDirection = SCROLL_DOWN;
Dimension size = scroller.getSize();
Point convertedPoint = SwingUtilities.convertPoint( scroller,
new Point( 1, (size.height - 1) - 2 ),
list );
int bottom = list.locationToIndex( convertedPoint );
valueIsAdjusting = true;
list.setSelectedIndex( bottom );
valueIsAdjusting = false;
AbstractAction timerAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
autoScrollDown();
}
public boolean isEnabled() {
return true;
}
};
autoscrollTimer = new Timer( 100, timerAction );
}
autoscrollTimer.start();
}
protected void stopAutoScrolling() {
isAutoScrolling = false;
if ( autoscrollTimer != null ) {
autoscrollTimer.stop();
autoscrollTimer = null;
}
}
protected void autoScrollUp() {
int index = list.getSelectedIndex();
if ( index > 0 ) {
valueIsAdjusting = true;
list.setSelectedIndex( index - 1 );
valueIsAdjusting = false;
list.ensureIndexIsVisible( index - 1 );
}
}
protected void autoScrollDown() {
int index = list.getSelectedIndex();
int lastItem = list.getModel().getSize() - 1;
if ( index < lastItem ) {
valueIsAdjusting = true;
list.setSelectedIndex( index + 1 );
valueIsAdjusting = false;
list.ensureIndexIsVisible( index + 1 );
}
}
//
// end Autoscroll methods
//=================================================================
//===================================================================
// begin Utility methods
//
/**
* This is is a utility method that helps event handlers figure out where to
* send the focus when the popup is brought up. The standard implementation
* delegates the focus to the editor (if the combo box is editable) or to
* the JComboBox if it is not editable.
*/
protected void delegateFocus( MouseEvent e ) {
if ( comboBox.isEditable() ) {
comboBox.getEditor().getEditorComponent().requestFocus();
}
else {
comboBox.requestFocus();
}
}
/**
* Makes the popup visible if it is hidden and makes it hidden if it is visible.
*/
protected void togglePopup() {
if ( isVisible() ) {
hide();
}
else {
show();
}
}
protected MouseEvent convertMouseEvent( MouseEvent e ) {
Point convertedPoint = SwingUtilities.convertPoint( (Component)e.getSource(),
e.getPoint(), list );
MouseEvent newEvent = new MouseEvent( (Component)e.getSource(),
e.getID(),
e.getWhen(),
e.getModifiers(),
convertedPoint.x,
convertedPoint.y,
e.getModifiers(),
e.isPopupTrigger() );
return newEvent;
}
protected int getPopupHeightForRowCount(int maxRowCount) {
int currentElementCount = comboBox.getModel().getSize();
if ( currentElementCount > 0 ) {
Rectangle r = list.getCellBounds(0,0);
if ( maxRowCount < currentElementCount )
return (r.height * maxRowCount) + 2;
else
return (r.height * currentElementCount) + 2;
}
else
return 100;
}
protected Rectangle computePopupBounds(int px,int py,int pw,int ph) {
Rectangle absBounds;
Rectangle r = new Rectangle(px,py,pw,ph);
boolean inModalDialog = inModalDialog();
/** Workaround for modal dialogs. See also JPopupMenu.java **/
if ( inModalDialog ) {
Dialog dlg = getDialog();
Point p;
if ( dlg instanceof JDialog ) {
JRootPane rp = ((JDialog)dlg).getRootPane();
p = rp.getLocationOnScreen();
absBounds = rp.getBounds();
absBounds.x = p.x;
absBounds.y = p.y;
}
else
absBounds = dlg.getBounds();
p = new Point(absBounds.x,absBounds.y);
SwingUtilities.convertPointFromScreen(p,comboBox);
absBounds.x = p.x;
absBounds.y = p.y;
}
else {
Point p;
Dimension scrSize = Toolkit.getDefaultToolkit().getScreenSize();
absBounds = new Rectangle();
p = new Point(0,0);
SwingUtilities.convertPointFromScreen(p,comboBox);
absBounds.x = p.x;
absBounds.y = p.y;
absBounds.width = scrSize.width;
absBounds.height= scrSize.height;
}
if ( SwingUtilities.isRectangleContainingRectangle(absBounds,r) )
return r;
else {
Rectangle r2 = new Rectangle(0,-r.height,r.width,r.height);
if ( SwingUtilities.isRectangleContainingRectangle(absBounds,r2) )
return r2;
if ( inModalDialog ) {
SwingUtilities.computeIntersection(absBounds.x,absBounds.y,absBounds.width,absBounds.height,r);
SwingUtilities.computeIntersection(absBounds.x,absBounds.y,absBounds.width,absBounds.height,r2);
if ( r.height > r2.height )
return r;
else
return r2;
}
else
return r2;
}
}
private Dialog getDialog() {
Container parent;
for ( parent = comboBox.getParent() ; parent != null && !(parent instanceof Dialog)
&& !(parent instanceof Window) ; parent = parent.getParent() );
if ( parent instanceof Dialog )
return (Dialog) parent;
else
return null;
}
private boolean inModalDialog() {
return (getDialog() != null);
}
/**
* A utility method used by the event listeners. Given a mouse event, it changes
* the list selection to the list item below the mouse.
*/
protected void updateListBoxSelectionForEvent(MouseEvent anEvent,boolean shouldScroll) {
Point location = anEvent.getPoint();
if ( list == null )
return;
int index = list.locationToIndex(location);
if ( index == -1 ) {
if ( location.y < 0 )
index = 0;
else
index = comboBox.getModel().getSize() - 1;
}
if ( list.getSelectedIndex() != index ) {
list.setSelectedIndex(index);
if ( shouldScroll )
list.ensureIndexIsVisible(index);
}
}
//
// end Utility methods
//=================================================================
}